﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;

namespace MVVMPlugin
{
    /// <summary>
    /// Main singleton class for plugin service
    /// </summary>
    public class PluginCatalogService
    {
        #region "Private Data Members"
        private static AggregateCatalog _aggregateCatalog;
        private Dictionary<string, DeploymentCatalog> _catalogs;
        private Collection<ExportLifetimeContext<object>> _contextCollection;
        #endregion "Private Data Members"

        #region "Constructors and Initialize()"
        /// <summary>
        /// Default constructor
        /// </summary>
        private PluginCatalogService()
        {
            _catalogs = new Dictionary<string, DeploymentCatalog>();
            _contextCollection = new Collection<ExportLifetimeContext<object>>();
            CompositionInitializer.SatisfyImports(this);
        }

        /// <summary>
        /// Static constructor
        /// </summary>
        static PluginCatalogService()
        {
            _aggregateCatalog = new AggregateCatalog();
            _aggregateCatalog.Catalogs.Add(new DeploymentCatalog());
            Container = new CompositionContainer(_aggregateCatalog);
            CompositionHost.Initialize(Container);

            Instance = new PluginCatalogService();
        }

        /// <summary>
        /// Initialize Method
        /// </summary>
        public static void Initialize()
        {           
        }
        #endregion "Constructors and Initialize()"

        #region "Public Properties"

        public static PluginCatalogService Instance { get; private set; }

        public static CompositionContainer Container { get; private set; }

        [ImportMany("MVVMPlugin", AllowRecomposition = true)]
        public IEnumerable<Lazy<object, IPluginMetadata>> PluginsLazy { get; set; }

        [ImportMany("MVVMPlugin", AllowRecomposition = true)]
        public IEnumerable<ExportFactory<object, IPluginMetadata>> PluginsFactories { get; set; }

        #endregion "Public Properties"

        #region "Public Methods for Add & Remove Xap"

        /// <summary>
        /// Method to add XAP
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="completedAction"></param>
        public void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null)
        {
            DeploymentCatalog catalog;
            if (!_catalogs.TryGetValue(uri, out catalog))
            {
                catalog = new DeploymentCatalog(uri);
                catalog.DownloadCompleted += (s, e) =>
                {
                    if (e.Error == null)
                    {
                        _catalogs.Add(uri, catalog);
                        _aggregateCatalog.Catalogs.Add(catalog);
                    }
                    else
                    {
                        throw new Exception(e.Error.Message, e.Error);
                    }
                };

                if (completedAction != null)
                    catalog.DownloadCompleted += (s, e) => completedAction(e);

                catalog.DownloadAsync();
            }
            else
            {
                if (completedAction != null)
                {
                    AsyncCompletedEventArgs e = new AsyncCompletedEventArgs(null, false, null);
                    completedAction(e);
                }
            }
        }

        /// <summary>
        /// Method to remove XAP
        /// </summary>
        /// <param name="uri"></param>
        public void RemoveXap(string uri)
        {
            DeploymentCatalog catalog;
            if (_catalogs.TryGetValue(uri, out catalog))
            {
                _aggregateCatalog.Catalogs.Remove(catalog);
                _catalogs.Remove(uri);
            }
        }

        #endregion "Public Methods for Add & Remove Xap"

        #region "Public Methods for Plugin"

        /// <summary>
        /// Method to get an instance of non-shared plugin
        /// </summary>
        /// <param name="pluginName"></param>
        /// <param name="pluginType"></param>
        /// <returns></returns>
        public object FindPlugin(string pluginName, PluginType? pluginType = null)
        {
            ExportLifetimeContext<object> context;
            if (pluginType == null)
            {
                context = PluginsFactories.Single(
                    n => (n.Metadata.Name == pluginName)).CreateExport();
            }
            else
            {
                context = PluginsFactories.Single(
                    n => (n.Metadata.Name == pluginName && n.Metadata.Type == pluginType)).CreateExport();
            }
            _contextCollection.Add(context);
            return context.Value;
        }

        /// <summary>
        /// Method to get an instance of non-shared plugin
        /// </summary>
        /// <param name="pluginName"></param>
        /// <param name="pluginType"></param>
        /// <returns></returns>
        public object TryFindPlugin(string pluginName, PluginType? pluginType = null)
        {
            ExportFactory<object, IPluginMetadata> exportFactory;
            if (pluginType == null)
            {
                exportFactory = PluginsFactories.SingleOrDefault(
                    n => (n.Metadata.Name == pluginName));
            }
            else
            {
                exportFactory = PluginsFactories.SingleOrDefault(
                    n => (n.Metadata.Name == pluginName && n.Metadata.Type == pluginType));
            }
            if (exportFactory == null) return null;
            ExportLifetimeContext<object> context = exportFactory.CreateExport();
            _contextCollection.Add(context);
            return context.Value;
        }

        /// <summary>
        /// Method to release non-shared plugin
        /// </summary>
        /// <param name="plugin"></param>
        /// <returns></returns>
        public bool ReleasePlugin(object plugin)
        {
            ExportLifetimeContext<object> context = _contextCollection.FirstOrDefault(n => n.Value.Equals(plugin));
            if (context == null) return false;
            _contextCollection.Remove(context);
            context.Dispose();
            return true;
        }

        /// <summary>
        /// Method to get access of shared plugin
        /// </summary>
        /// <param name="pluginName"></param>
        /// <param name="pluginType"></param>
        /// <returns></returns>
        public object FindSharedPlugin(string pluginName, PluginType? pluginType = null)
        {
            Lazy<object, IPluginMetadata> lazy;
            if (pluginType == null)
            {
                lazy = PluginsLazy.Single(
                    n => (n.Metadata.Name == pluginName));
            }
            else
            {
                lazy = PluginsLazy.Single(
                    n => (n.Metadata.Name == pluginName && n.Metadata.Type == pluginType));
            }
            return lazy.Value;
        }

        /// <summary>
        /// Method to get access of shared plugin
        /// </summary>
        /// <param name="pluginName"></param>
        /// <param name="pluginType"></param>
        /// <returns></returns>
        public object TryFindSharedPlugin(string pluginName, PluginType? pluginType = null)
        {
            Lazy<object, IPluginMetadata> lazy;
            if (pluginType == null)
            {
                lazy = PluginsLazy.SingleOrDefault(
                    n => (n.Metadata.Name == pluginName));
            }
            else
            {
                lazy = PluginsLazy.SingleOrDefault(
                    n => (n.Metadata.Name == pluginName && n.Metadata.Type == pluginType));
            }
            return lazy == null ? null : lazy.Value;
        }

        #endregion "Public Methods for Plugin"
    }
}
